package com.caregiver.watch.business.user.interceptor;

import com.caregiver.watch.business.user.config.properties.IgnoreWhiteProperties;
import com.caregiver.watch.business.user.context.SecurityContextHolder;
import com.caregiver.watch.business.user.model.dto.LoginUser;
import com.caregiver.watch.business.user.service.impl.TokenService;
import com.caregiver.watch.business.user.utils.SecurityUtils;
import com.caregiver.watch.common.constant.SecurityConstants;
import com.caregiver.watch.common.redis.service.RedisService;
import com.caregiver.watch.common.utils.jwt.JwtUtils;
import com.caregiver.watch.common.utils.servlet.ServletUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.Set;

/**
 * @Description: 鉴权拦截器
 * @Author 疆戟
 * @Date 2025/8/15 23:11
 * @Version 1.0
 */
@Component
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisService redisService;
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;
    @Autowired
    private TokenService tokenService;

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI();
        // 跳过不需要验证的路径
        if (matches(url, ignoreWhite.getWhites())) {
            return true;
        }
        String token = SecurityUtils.getToken(request);
        if (StringUtils.isEmpty(token)) {
            unauthorizedResponse(response, url, "令牌不能为空");
            return false;
        }
        Claims claims = JwtUtils.parseToken(token);
        if (claims == null) {
            unauthorizedResponse(response,  url,  "令牌已过期或验证不正确！");
            return false;
        }
        String userKey = JwtUtils.getUserKey(claims);
        boolean isLogin = redisService.hasKey(SecurityUtils.getTokenKey(userKey));
        if (!isLogin) {
            unauthorizedResponse(response,  url,  "登录状态已过期");
            return false;
        }
        String userId = JwtUtils.getUserId(claims);
        String username = JwtUtils.getUserName(claims);
        if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(username)) {
            unauthorizedResponse(response,  url,  "令牌验证失败");
            return false;
        }
        SecurityContextHolder.setUserId(userId);
        SecurityContextHolder.setUserName(username);
        SecurityContextHolder.setUserKey(userKey);
        if (StringUtils.isNotBlank(token)) {
            LoginUser loginUser = tokenService.getLoginUser(token);
            if (Objects.nonNull(loginUser)) {
                tokenService.verifyToken(loginUser);
                SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
            }
        }
        return true;
    }

    /**
     * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
     *
     * @param path  path
     * @param ignoreWhite 忽略的白名单
     * @return 是否匹配
     */
    private static boolean matches(String path, Set<String> ignoreWhite) {
        if (StringUtils.isBlank(path) || CollectionUtils.isEmpty(ignoreWhite)) {
            return false;
        }
        for (String pattern : ignoreWhite) {
            if (isMatch(pattern, path)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断url是否与规则配置:
     * ? 表示单个字符;
     * * 表示一层路径内的任意字符串，不可跨层级;
     * ** 表示任意层路径;
     *
     * @param pattern 匹配规则
     * @param url 需要匹配的url
     * @return
     */
    public static boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }

    /**
     * 响应异常信息
     *
     * @param response 响应
     * @param path 请求路径
     * @param msg 错误信息
     */
    private void unauthorizedResponse(HttpServletResponse response, String path, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", path);
        try {
            // 设置403状态码
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            // 设置响应内容类型
            response.setContentType("application/json;charset=UTF-8");
            // 写入错误信息
            response.getWriter().write("{\"code\":403,\"message\":\"" + msg + "\"}");
        } catch (IOException e) {
            log.error("响应写入异常", e);
        }
    }
}
